#!/usr/bin/python
#
#  Python utilities shared by the build scripts.
#

import datetime
import json

class BitEncoder:
	"Bitstream encoder."

	_bits = None

	def __init__(self):
		self._bits = []

	def bits(self, x, nbits):
		if (x >> nbits) != 0:
			raise Exception('input value has too many bits (value: %d, bits: %d)' % (x, nbits))
		for i in xrange(nbits):
			t = (x >> (nbits - i - 1)) & 0x01
			self._bits.append(t)

	def string(self, x):
		nbits = len(x) * 8

		for i in xrange(nbits):
			byteidx = i / 8
			bitidx = i % 8
			if byteidx < 0 or byteidx >= len(x):
				self._bits.append(0)
			else:
				t = (ord(x[byteidx]) >> (7 - bitidx)) & 0x01
				self._bits.append(t)

	def getNumBits(self):
		"Get current number of encoded bits."
		return len(self._bits)

	def getNumBytes(self):
		"Get current number of encoded bytes, rounded up."
		nbits = len(self._bits)
		while (nbits % 8) != 0:
			nbits += 1
		return nbits / 8

	def getBytes(self):
		"Get current bitstream as a byte sequence, padded with zero bits."
		bytes = []

		for i in xrange(self.getNumBytes()):
			t = 0
			for j in xrange(8):
				off = i*8 + j
				if off >= len(self._bits):
					t = (t << 1)
				else:
					t = (t << 1) + self._bits[off]
			bytes.append(t)

		return bytes

	def getByteString(self):
		"Get current bitstream as a string."
		return ''.join([chr(i) for i in self.getBytes()])

class GenerateC:
	"Helper for generating C source and header files."

	_data = None
	wrap_col = 76

	def __init__(self):
		self._data = []

	def emitRaw(self, text):
		"Emit raw text (without automatic newline)."
		self._data.append(text)

	def emitLine(self, text):
		"Emit a raw line (with automatic newline)."
		self._data.append(text + '\n')

	def emitHeader(self, autogen_by):
		"Emit file header comments."

		# Note: a timestamp would be nice but it breaks incremental building
		self.emitLine('/*')
		self.emitLine(' *  Automatically generated by %s, do not edit!' % autogen_by)
		self.emitLine(' */')
		self.emitLine('')

	def emitArray(self, data, tablename, visibility=None, typename='char', size=None, intvalues=False, const=True):
		"Emit an array as a C array."

		# lenient input
		if isinstance(data, unicode):
			data = data.encode('utf-8')
		if isinstance(data, str):
			tmp = []
			for i in xrange(len(data)):
				tmp.append(ord(data[i]))
			data = tmp

		size_spec = ''
		if size is not None:
			size_spec = '%d' % size
		visib_qual = ''
		if visibility is not None:
			visib_qual = visibility + ' '
		const_qual = ''
		if const:
			const_qual = 'const '
		self.emitLine('%s%s%s %s[%s] = {' % (visib_qual, const_qual, typename, tablename, size_spec))

		line = ''
		for i in xrange(len(data)):
			if intvalues:
				t = "%d," % data[i]
			else:
				t = "(%s)'\\x%02x', " % (typename, data[i])
			if len(line) + len(t) >= self.wrap_col:
				self.emitLine(line)
				line = t
			else:
				line += t
		if line != '':
			self.emitLine(line)
		self.emitLine('};')

	def emitDefine(self, name, value, comment=None):
		"Emit a C define with an optional comment."

		# XXX: there is no escaping right now (for comment or value)
		if comment is not None:
			self.emitLine('#define %-60s  %-30s /* %s */' % (name, value, comment))
		else:
			self.emitLine('#define %-60s  %s' % (name, value))

	def getString(self):
		"Get the entire file as a string."
		return ''.join(self._data)

def json_encode(x):
	"JSON encode a value."
	try:
		return json.dumps(x)
	except AttributeError:
		pass

	# for older library versions
	return json.write(x)

def json_decode(x):
	"JSON decode a value."
	try:
		return json.loads(x)
	except AttributeError:
		pass

	# for older library versions
	return json.read(x)

